/*******************************************************************************
* This file provides the API for AT26DF081A serial flash memory.
* These API must only be used after SPI 1 has been initialized.
*
* Author: Kong Wai Weng
* Email:  waiweng83@gmail.com
*******************************************************************************/



#include "hal_serialflash.h"



/*******************************************************************************
* PRIVATE GLOBAL VARIABLES                                                     *
*******************************************************************************/

// Command Opcode.
#define READ_ARRAY		0x03
#define CHIP_ERASE		0x60
#define PAGE_PROGRAM	0x02
#define WRITE_ENABLE	0x06
#define WRITE_DISABLE	0x04
#define READ_STATUS		0x05
#define WRITE_STATUS	0x01



/*******************************************************************************
* PRIVATE FUNCTION PROTOTYPES                                                  *
*******************************************************************************/

static void prv_vWriteEnable(void);



/*******************************************************************************
* PUBLIC FUNCTION: vSFlashChipErase
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Erase the entire memory.
* This is a blocking function and should not be called from ISR.
*
*******************************************************************************/
void vSFlashChipErase(void)
{	
	// If we are calling from task, take control of serial flash.
	// Wait forever until the resource is available.
	if (xTaskGetCurrentTaskHandle() != NULL) {
		xSemaphoreTake(mutexSerialFlashBusy, portMAX_DELAY);
	}	
	
	// Make sure the serial flash is not busy.
	while (ucSFlashBusy() == 1) {
		// If we are calling from task, delay for 10ms to allow other task to run.
		if (xTaskGetCurrentTaskHandle() != NULL) {
			vTaskDelay(configTICK_RATE_HZ / 100);
		}	
	}	
	
	// Set the write enable.
	prv_vWriteEnable();
	
	// Erase the entire memory.
	SFLASH_CSN = 0;
	vWriteSpi1(CHIP_ERASE);
	SFLASH_CSN = 1;
	
	// Make sure the serial flash is not busy.
	while (ucSFlashBusy() == 1) {
		// If we are calling from task, delay for 10ms to allow other task to run.
		if (xTaskGetCurrentTaskHandle() != NULL) {
			vTaskDelay(configTICK_RATE_HZ / 100);
		}	
	}	
	
	// If we are calling from task, release control of serial flash.
	if (xTaskGetCurrentTaskHandle() != NULL) {
		xSemaphoreGive(mutexSerialFlashBusy);
	}	
}	



/*******************************************************************************
* PUBLIC FUNCTION: vSFlashRead
*
* PARAMETERS:
* ~ ulAddress	- The source address (0x0 - 0xFFFFF).
* ~ uiLength	- Number of bytes to read.
* ~ pucBuffer	- Buffer for the data.
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Read from the serial flash.
* This is a blocking function and should not be called from ISR.
*
*******************************************************************************/
void vSFlashRead(unsigned long ulAddress, unsigned int uiLength, unsigned char* pucBuffer)
{
	// If we are calling from task, take control of serial flash.
	// Wait forever until the resource is available.
	if (xTaskGetCurrentTaskHandle() != NULL) {
		xSemaphoreTake(mutexSerialFlashBusy, portMAX_DELAY);
	}	
	
	// Make sure the serial flash is not busy.
	while (ucSFlashBusy() == 1) {
		// If we are calling from task, delay for 10ms to allow other task to run.
		if (xTaskGetCurrentTaskHandle() != NULL) {
			vTaskDelay(configTICK_RATE_HZ / 100);
		}	
	}	
	
	// Read from the memory.
	SFLASH_CSN = 0;
	vWriteSpi1(READ_ARRAY);
	vWriteSpi1((ulAddress >> 16) & 0xff);
	vWriteSpi1((ulAddress >> 8) & 0xff);
	vWriteSpi1(ulAddress & 0xff);
	while (uiLength-- > 0) {
		*pucBuffer++ = uiReadSpi1();
	}	
	SFLASH_CSN = 1;
	
	// If we are calling from task, release control of serial flash.
	if (xTaskGetCurrentTaskHandle() != NULL) {
		xSemaphoreGive(mutexSerialFlashBusy);
	}	
}	



/*******************************************************************************
* PUBLIC FUNCTION: vSFlashWrite
*
* PARAMETERS:
* ~ ulAddress	- The destination address (0x0 - 0xFFFFF).
* ~ uiLength	- Number of bytes to write.
* ~ pucBuffer	- Buffer for the data.
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Write to the serial flash.
* This is a blocking function and should not be called from ISR.
*
*******************************************************************************/
void vSFlashWrite(unsigned long ulAddress, unsigned int uiLength, unsigned char* pucBuffer)
{
	// If we are calling from task, take control of serial flash.
	// Wait forever until the resource is available.
	if (xTaskGetCurrentTaskHandle() != NULL) {
		xSemaphoreTake(mutexSerialFlashBusy, portMAX_DELAY);
	}	
	
	// Make sure the serial flash is not busy.
	while (ucSFlashBusy() == 1) {
		// If we are calling from task, delay for 10ms to allow other task to run.
		if (xTaskGetCurrentTaskHandle() != NULL) {
			vTaskDelay(configTICK_RATE_HZ / 100);
		}	
	}	

	// Set the write enable.
	prv_vWriteEnable();
	
	// Write to the memory.
	SFLASH_CSN = 0;
	vWriteSpi1(PAGE_PROGRAM);
	vWriteSpi1((ulAddress >> 16) & 0xff);
	vWriteSpi1((ulAddress >> 8) & 0xff);
	vWriteSpi1(ulAddress & 0xff);
	while (uiLength-- > 0) {
		vWriteSpi1(*pucBuffer++);
		
		// If we cross the page boundary, we need to resend the command.
		// This is a recursive call.
		if ((++ulAddress & 0xff) == 0) {
			SFLASH_CSN = 1;
			
			// If we are calling from task, release control of serial flash.
			if (xTaskGetCurrentTaskHandle() != NULL) {
				xSemaphoreGive(mutexSerialFlashBusy);
			}	
			
			vSFlashWrite(ulAddress, uiLength, pucBuffer);
			return;
		}	
	}	
	SFLASH_CSN = 1;
	
	// If we are calling from task, release control of serial flash.
	if (xTaskGetCurrentTaskHandle() != NULL) {
		xSemaphoreGive(mutexSerialFlashBusy);
	}	
}	



/*******************************************************************************
* PUBLIC FUNCTION: ucSFlashReadStatus
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ Content of the status register.
*
* DESCRIPTIONS:
* Read the status register of the serial flash.
*
*******************************************************************************/
unsigned char ucSFlashReadStatus(void)
{
	unsigned char ucBuffer;
	
	// Read from the memory.
	SFLASH_CSN = 0;
	vWriteSpi1(READ_STATUS);
	ucBuffer = uiReadSpi1();
	SFLASH_CSN = 1;
	
	return ucBuffer;
}	



/*******************************************************************************
* PUBLIC FUNCTION: vSFlashWriteStatus
*
* PARAMETERS:
* ~ ucData	- Data we want to write.
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Write the status register of the serial flash.
*
*******************************************************************************/
void vSFlashWriteStatus(unsigned char ucData)
{	
	// Set the write enable.
	prv_vWriteEnable();
	
	SFLASH_CSN = 0;
	vWriteSpi1(WRITE_STATUS);
	vWriteSpi1(ucData);
	SFLASH_CSN = 1;
}	



/*******************************************************************************
* PUBLIC FUNCTION: vSFlashGlobalUnprotect
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Unprotect the whole memory of the serial flash.
*
*******************************************************************************/
void vSFlashGlobalUnprotect(void)
{
	vSFlashWriteStatus(0x00);
}	



/*******************************************************************************
* PUBLIC FUNCTION: ucSFlashBusy
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ Return 0 if ready.
* ~ Return 1 if busy.
*
* DESCRIPTIONS:
* Check whether the serial flash is busy.
*
*******************************************************************************/
unsigned char ucSFlashBusy(void)
{
	return (ucSFlashReadStatus() & 0x01);
}	



/*******************************************************************************
* PRIVATE FUNCTION: prv_vWriteEnable
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Set the write enable.
*
*******************************************************************************/
static void prv_vWriteEnable(void)
{
	SFLASH_CSN = 0;
	vWriteSpi1(WRITE_ENABLE);
	SFLASH_CSN = 1;
}	
